/**
*	Header: actor.h
*	
*	This header defines the common actor object for all people, monsters, etc. (actors) and
*	derived actors.
*
*	TODO: Split derived actor classes into separate files?
*	
*/

#pragma once

#include "common.h"
#include "object.h"
#include "events.h"
#include <list>
#include <queue>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int_distribution.hpp>

enum factions {
	PLAYER_FACTION,
	GENERIC_NPC_FACTION
};

class Actor {
public:
	Actor();
	~Actor();
	Actor(unsigned short x, unsigned short y);
	Actor(unsigned short x, unsigned short y, TileMap* map);
	unsigned short getX() const {return posX;}
	unsigned short getY() const {return posY;}
	unsigned char getSym() {return symbol;}
	const factions getFaction() {return faction;};
	void modPos (short x = 0, short y = 0, TileMap* map = nullptr);
	virtual void update(TileMap* map, std::list <Actor*>* actorsList);
	virtual void takeItem(Tile* tile, bool all = false);
	virtual void takeItem(std::list<Item*>* itemList, bool all = false);
	virtual void takeItem(std::list<Item*>* itemList, std::list<Item*>::iterator it);
	virtual void hurt(unsigned short dam);
	bool isDead() {if (health <= 0) return true; else return false;}
	int ID;
protected:
	unsigned char symbol; //ASCII
	/*unsigned*/ int posX;  // Freakin' libtcod.
	/*unsigned*/ int posY;
	unsigned int health;
	TileMap* deathMap; // eliminate redundancy eventually

	factions faction;
	std::list<Item*> inventory;
	TCODPath* path;
};

Actor::Actor() {
	static int ActorNumber = 0;
	ID = ActorNumber;
	ActorNumber++;
	symbol = '@';
	posX = 1;
	posY = 1;
	path = nullptr;
	faction = GENERIC_NPC_FACTION;
	deathMap = nullptr;
	health = 30;
}

Actor::~Actor() {
	delete path;
	if (deathMap!=nullptr) {
		deathMap->getTile(posX,posY)->actorList.remove(this);
		std::cout << "Tile reference removed.\n";
	}
	inventory.clear();
}

Actor::Actor(unsigned short x, unsigned short y) {
	static int ActorNumber = 0;
	ID = ActorNumber;
	ActorNumber++;
	symbol = '@';
	posX = x;
	posY = y;
	path = nullptr;
	faction = GENERIC_NPC_FACTION;
	deathMap = nullptr;
	health = 30;
}

Actor::Actor(unsigned short x, unsigned short y, TileMap* map) {
	static int ActorNumber = 0;
	ID = ActorNumber;
	ActorNumber++;
	symbol = '@';
	posX = x;
	posY = y;
	map->getTile(x,y)->actorList.push_back(this);
	path = new TCODPath(map->getTCODMap());
	faction = GENERIC_NPC_FACTION;
	deathMap = map;
	health = 30;
}

void Actor::modPos (short x, short y, TileMap* map) { // Add x y numbers to pos X and Y
	if(map!=nullptr)map->getTile(posX,posY)->actorList.remove(this); // Branch prediction performance issue?
	else std::cerr << "No valid map pointer given for actor " << ID << "! Only adjusting position - beware of actor-tilecontent inconsistencies.\n";
	posX += x;
	posY += y;
	if(map!=nullptr)map->getTile(posX,posY)->actorList.push_back(this);
}

void Actor::update(TileMap* map, std::list <Actor*>* actorsList) {
	deathMap = map;
	/*static boost::random::mt19937 gen;
	boost::random::uniform_int_distribution<> distX(0, map->getWidth()-1);
	boost::random::uniform_int_distribution<> distY(0, map->getHeight()-1);
	if(path==nullptr)
		path = new TCODPath(map->getTCODMap());
	if(path->isEmpty())
		path->compute(posX,posY,distX(gen),distY(gen));
	else {
		map->getTile(posX,posY)->actorList.remove(this);
		path->walk(&posX,&posY,true);
		map->getTile(posX,posY)->actorList.push_back(this);
		if (!map->getTile(posX, posY)->itemList.empty()) {
			takeItem(map->getTile(posX, posY));
			std::cout << "NPC " << ID << "'s inventory size is now: " << inventory.size() << "\n";
		}
	}*/


	/*map->computeFov(posX,posY);
	if (dist(gen) == 1) {
		//if (map->getTile(posX, posY+1)->actorList.empty() != true) std::cout << "Actor collided with actor number " << map->getTile(posX, posY+1)->actorList.front()->ID <<"!\n";
		if (map->getTile(posX, posY+1)->passable == true) modPos(0,1,map);
	}
	if (dist(gen) == 2) {
		//if (map->getTile(posX, posY-1)->actorList.empty() != true) std::cout << "Actor collided with actor number " << map->getTile(posX, posY-1)->actorList.front()->ID <<"!\n";
		if (map->getTile(posX, posY-1)->passable == true) modPos(0,-1,map);
	}
	if (dist(gen) == 3) {
		//if (map->getTile(posX-1, posY)->actorList.empty() != true) std::cout << "Actor collided with actor number " << map->getTile(posX-1, posY)->actorList.front()->ID <<"!\n";
		if (map->getTile(posX-1, posY)->passable == true) modPos(-1,0,map);
	}
	if (dist(gen) == 4) {
		//if (map->getTile(posX+1, posY)->actorList.empty() != true) std::cout << "Actor collided with actor number " << map->getTile(posX+1, posY)->actorList.front()->ID <<"!\n";
		if (map->getTile(posX+1, posY)->passable == true) modPos(1,0,map);
	}*/
}

void Actor::takeItem(Tile* tile, bool all) {
	if (all) {
		inventory.splice(inventory.end(),tile->itemList);
	}
	else {
		inventory.push_back(tile->itemList.back());
		tile->itemList.pop_back();
	}
}

void Actor::takeItem(std::list<Item*>* itemList, bool all) {
	if (all) {
		inventory.splice(inventory.end(),*itemList);
	}
	else {
		inventory.push_back(itemList->back());
		itemList->pop_back();
	}
}

void Actor::takeItem(std::list<Item*>* itemList, std::list<Item*>::iterator it) {
	inventory.push_back(*it);
	itemList->erase(it);
}

void Actor::hurt(unsigned short dam) {
//	if ((health > 0)&&((int)health-(int)dam >= 0)) {
		health = health-dam;
	//}
	//else dead = true;
}

class NPC : public Actor {
public:
	const std::string& getDialoguePath() {return dialogueFile;}
protected:
	std::string dialogueFile;
	
};

class Player : public Actor {
public:
	Player();

	void update(TileMap* map, std::list <Actor*>* actorsList) {};
	void handleInput (TCOD_key_t key, TileMap* map);
protected:

private:
};

Player::Player() {
	posX = gameSettings::windowX/2;
	posY = gameSettings::windowY/2;
	faction = PLAYER_FACTION;
}

void Player::handleInput (TCOD_key_t key, TileMap* map) {
	if ((key.vk == TCODK_DOWN)&&(key.pressed)) {
		if (map->getTile(posX, posY+1)->actorList.empty() == false) map->getTile(posX, posY+1)->actorList.back()->hurt(10);
		else if (map->getTile(posX, posY+1)->passable == true) modPos(0,1,map);
		if (!map->getTile(posX, posY)->itemList.empty()) {
			takeItem(map->getTile(posX, posY));
			std::cout << "Inventory size is now: " << inventory.size() << "\n";
		}
	}
	if ((key.vk == TCODK_UP)&&(key.pressed)) {
		if (map->getTile(posX, posY-1)->actorList.empty() == false) map->getTile(posX, posY-1)->actorList.back()->hurt(10);
		else if (map->getTile(posX, posY-1)->passable == true) modPos(0,-1,map);
		if (!map->getTile(posX, posY)->itemList.empty()) {
			takeItem(map->getTile(posX, posY));
			std::cout << "Inventory size is now: " << inventory.size() << "\n";
		}
	}
	if ((key.vk == TCODK_LEFT)&&(key.pressed)) {
		if (map->getTile(posX-1, posY)->actorList.empty() == false) map->getTile(posX-1, posY)->actorList.back()->hurt(10);
		else if (map->getTile(posX-1, posY)->passable == true) modPos(-1,0,map);
		if (!map->getTile(posX, posY)->itemList.empty()) {
			takeItem(map->getTile(posX, posY));
			std::cout << "Inventory size is now: " << inventory.size() << "\n";
		}
	}
	if ((key.vk == TCODK_RIGHT)&&(key.pressed)) {
		if (map->getTile(posX+1, posY)->actorList.empty() == false) map->getTile(posX+1, posY)->actorList.back()->hurt(10);
		else if (map->getTile(posX+1, posY)->passable == true) modPos(1,0,map);
		if (!map->getTile(posX, posY)->itemList.empty()) {
			takeItem(map->getTile(posX, posY));
			std::cout << "Inventory size is now: " << inventory.size() << "\n";
		}
	}
	/*TCODMouse TCODMouse;
	if (TCODMouse.getStatus().lbutton_pressed==true) {
		if (map->getTile(TCODMouse.getStatus().cx, TCODMouse.getStatus().cy)->passable == true){
			if (path==nullptr)
				path = new TCODPath(map->getTCODMap());
			path->compute(posX,posY,TCODMouse.getStatus().cx, TCODMouse.getStatus().cy);
		};
	}
	else if ((path!=nullptr)&&(path->isEmpty()==false)) {
		map->getTile(posX,posY)->actorList.remove(this);
		path->walk(&posX,&posY,true);
		map->getTile(posX,posY)->actorList.push_back(this);
		if (!map->getTile(posX, posY)->itemList.empty()) {
			inventory.push_back(map->getTile(posX, posY)->itemList.back());
			map->getTile(posX, posY)->itemList.pop_back();
			std::cout << "Inventory size is now: " << inventory.size() << "\n";
		}
	}*/
}

class TestHostile: public Actor {
public:
	TestHostile(unsigned short x, unsigned short y, TileMap* map);

	void update(TileMap* map, std::list <Actor*>* actorsList);

protected:
	Actor* target;
};

TestHostile::TestHostile(unsigned short x, unsigned short y, TileMap* map) {
	static int ActorNumber = 0;
	ID = ActorNumber;
	ActorNumber++;
	symbol = '@';
	posX = x;
	posY = y;
	map->getTile(x,y)->actorList.push_back(this);
	path = new TCODPath(map->getTCODMap());
	faction = GENERIC_NPC_FACTION;
	health = 1000;
	target = nullptr;
}

void TestHostile::update(TileMap* map, std::list <Actor*>* actorsList) {
	static boost::random::mt19937 gen;
	boost::random::uniform_int_distribution<> distX(0, map->getWidth()-1);
	boost::random::uniform_int_distribution<> distY(0, map->getHeight()-1);
	map->computeFov(posX,posY);
	if (path==nullptr)
		path = new TCODPath(map->getTCODMap());
	if (path->isEmpty()) {
		if (target!=nullptr)
			if ((target->getX() == posX)&&(target->getY() == posY)&&!(target->isDead())) {
				std::cout << "damaged player!\n";
				target->hurt(10);
			};
		if((target == nullptr)||(target->isDead())) {
			std::list<Actor*>::iterator iterator;
			for (iterator = actorsList->begin(); iterator != actorsList->end(); ++iterator) {
				if (((*iterator)->getFaction() == PLAYER_FACTION)&&(map->isInFov((*iterator)->getX(),(*iterator)->getY()))) {
					target = (*iterator);
					path->compute(posX,posY,target->getX(),target->getY());
				}
			}
		}
		if((target == nullptr)||(target->isDead()))
			path->compute(posX,posY,distX(gen),distY(gen));
	}
	else {
		map->getTile(posX,posY)->actorList.remove(this);
		path->walk(&posX,&posY,true);
		map->getTile(posX,posY)->actorList.push_back(this);
		if (!map->getTile(posX, posY)->itemList.empty()) {
			takeItem(map->getTile(posX, posY));
			std::cout << "NPC " << ID << "'s inventory size is now: " << inventory.size() << "\n";
		}
	}
}